{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In google collab, uncomment this:\n",
    "# !wget https://bit.ly/2FMJP5K -q -O setup.py\n",
    "# !bash setup.py 2>&1 1>stdout.log | tee stderr.log\n",
    "\n",
    "# This code creates a virtual display to draw game images on.\n",
    "# If you are running locally, just ignore it\n",
    "import os\n",
    "if type(os.environ.get(\"DISPLAY\")) is not str or len(os.environ.get(\"DISPLAY\")) == 0:\n",
    "    !bash ../xvfb start\n",
    "    os.environ['DISPLAY'] = ':1'\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Seminar: Monte-carlo tree search (5 pts)\n",
    "\n",
    "In this seminar, we'll implement a vanilla MCTS planning and use it to solve some Gym envs.\n",
    "\n",
    "But before we do that, we first need to modify gym env to allow saving and loading game states to facilitate backtracking."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import gym\n",
    "from gym.core import Wrapper\n",
    "from pickle import dumps, loads\n",
    "from collections import namedtuple\n",
    "\n",
    "# a container for get_result function below. Works just like tuple, but prettier\n",
    "ActionResult = namedtuple(\n",
    "    \"action_result\", (\"snapshot\", \"observation\", \"reward\", \"is_done\", \"info\"))\n",
    "\n",
    "\n",
    "class WithSnapshots(Wrapper):\n",
    "    \"\"\"\n",
    "    Creates a wrapper that supports saving and loading environemnt states.\n",
    "    Required for planning algorithms.\n",
    "\n",
    "    This class will have access to the core environment as self.env, e.g.:\n",
    "    - self.env.reset()           #reset original env\n",
    "    - self.env.ale.cloneState()  #make snapshot for atari. load with .restoreState()\n",
    "    - ...\n",
    "\n",
    "    You can also use reset() and step() directly for convenience.\n",
    "    - s = self.reset()                   # same as self.env.reset()\n",
    "    - s, r, done, _ = self.step(action)  # same as self.env.step(action)\n",
    "    \n",
    "    Note that while you may use self.render(), it will spawn a window that cannot be pickled.\n",
    "    Thus, you will need to call self.close() before pickling will work again.\n",
    "    \"\"\"\n",
    "\n",
    "    def get_snapshot(self, render=False):\n",
    "        \"\"\"\n",
    "        :returns: environment state that can be loaded with load_snapshot \n",
    "        Snapshots guarantee same env behaviour each time they are loaded.\n",
    "\n",
    "        Warning! Snapshots can be arbitrary things (strings, integers, json, tuples)\n",
    "        Don't count on them being pickle strings when implementing MCTS.\n",
    "\n",
    "        Developer Note: Make sure the object you return will not be affected by \n",
    "        anything that happens to the environment after it's saved.\n",
    "        You shouldn't, for example, return self.env. \n",
    "        In case of doubt, use pickle.dumps or deepcopy.\n",
    "\n",
    "        \"\"\"\n",
    "        if render:\n",
    "            self.render()  # close popup windows since we can't pickle them\n",
    "            self.close()\n",
    "            \n",
    "        if self.unwrapped.viewer is not None:\n",
    "            self.unwrapped.viewer.close()\n",
    "            self.unwrapped.viewer = None\n",
    "        return dumps(self.env)\n",
    "\n",
    "    def load_snapshot(self, snapshot, render=False):\n",
    "        \"\"\"\n",
    "        Loads snapshot as current env state.\n",
    "        Should not change snapshot inplace (in case of doubt, deepcopy).\n",
    "        \"\"\"\n",
    "\n",
    "        assert not hasattr(self, \"_monitor\") or hasattr(\n",
    "            self.env, \"_monitor\"), \"can't backtrack while recording\"\n",
    "\n",
    "        if render:\n",
    "            self.render()  # close popup windows since we can't load into them\n",
    "            self.close()\n",
    "        self.env = loads(snapshot)\n",
    "\n",
    "    def get_result(self, snapshot, action):\n",
    "        \"\"\"\n",
    "        A convenience function that \n",
    "        - loads snapshot, \n",
    "        - commits action via self.step,\n",
    "        - and takes snapshot again :)\n",
    "\n",
    "        :returns: next snapshot, next_observation, reward, is_done, info\n",
    "\n",
    "        Basically it returns next snapshot and everything that env.step would have returned.\n",
    "        \"\"\"\n",
    "\n",
    "        <your code here load, commit, take snapshot >\n",
    "\n",
    "        return ActionResult(< next_snapshot > ,    #fill in the variables\n",
    "                            < next_observation > ,\n",
    "                            < reward > , < is_done > , < info > )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Try out snapshots:\n",
    "Let`s check our wrapper. At first, reset environment and save it, further randomly play some actions and restore our environment from the snapshot. It should be the same as our previous initial state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# make env\n",
    "env = WithSnapshots(gym.make(\"CartPole-v0\"))\n",
    "env.reset()\n",
    "\n",
    "n_actions = env.action_space.n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"initial_state:\")\n",
    "plt.imshow(env.render('rgb_array'))\n",
    "env.close()\n",
    "\n",
    "# create first snapshot\n",
    "snap0 = env.get_snapshot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# play without making snapshots (faster)\n",
    "while True:\n",
    "    is_done = env.step(env.action_space.sample())[2]\n",
    "    if is_done:\n",
    "        print(\"Whoops! We died!\")\n",
    "        break\n",
    "\n",
    "print(\"final state:\")\n",
    "plt.imshow(env.render('rgb_array'))\n",
    "env.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# reload initial state\n",
    "env.load_snapshot(snap0)\n",
    "\n",
    "print(\"\\n\\nAfter loading snapshot\")\n",
    "plt.imshow(env.render('rgb_array'))\n",
    "env.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# get outcome (snapshot, observation, reward, is_done, info)\n",
    "res = env.get_result(snap0, env.action_space.sample())\n",
    "\n",
    "snap1, observation, reward = res[:3]\n",
    "\n",
    "# second step\n",
    "res2 = env.get_result(snap1, env.action_space.sample())"
   ]
  },
  {
   "attachments": {
    "MCTS.png": {
     "image/png": "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"
    }
   },
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# MCTS: Monte-Carlo tree search\n",
    "\n",
    "![MCTS.png](attachment:MCTS.png)\n",
    "\n",
    "In this section, we'll implement the vanilla MCTS algorithm with UCB1-based node selection.\n",
    "\n",
    "$$\n",
    "\\dot{v_a} = v_a + C_n \\sqrt{\\frac{2 \\log {N}}{n_a}}\n",
    "$$\n",
    "\n",
    "where: \n",
    "- $N$ - number of time-steps so far,\n",
    "- $n_a$ - times action a is taken\n",
    "- $C_n$ - balance between exploration-exploitation, for $R \\in [0,1]$ $ C_p = \\frac{1}{\\sqrt{2}}$ \n",
    "[paper](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.374.1202&rep=rep1&type=pdf)\n",
    "\n",
    "We will start by implementing the `Node` class - a simple class that acts like MCTS node and supports some of the MCTS algorithm steps.\n",
    "\n",
    "This MCTS implementation makes some assumptions about the environment, you can find those _in the notes section at the end of the notebook_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert isinstance(env, WithSnapshots)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Node:\n",
    "    \"\"\" a tree node for MCTS \"\"\"\n",
    "\n",
    "    # metadata:\n",
    "    parent = None  # parent Node\n",
    "    value_sum = 0.  # sum of state values from all visits (numerator)\n",
    "    times_visited = 0  # counter of visits (denominator)\n",
    "\n",
    "    def __init__(self, parent, action,):\n",
    "        \"\"\"\n",
    "        Creates and empty node with no children.\n",
    "        Does so by commiting an action and recording outcome.\n",
    "\n",
    "        :param parent: parent Node\n",
    "        :param action: action to commit from parent Node\n",
    "\n",
    "        \"\"\"\n",
    "\n",
    "        self.parent = parent\n",
    "        self.action = action\n",
    "        self.children = set()  # set of child nodes\n",
    "\n",
    "        # get action outcome and save it\n",
    "        res = env.get_result(parent.snapshot, action)\n",
    "        self.snapshot, self.observation, self.immediate_reward, self.is_done, _ = res\n",
    "\n",
    "    def is_leaf(self):\n",
    "        return len(self.children) == 0\n",
    "\n",
    "    def is_root(self):\n",
    "        return self.parent is None\n",
    "\n",
    "    def get_mean_value(self):\n",
    "        return self.value_sum / self.times_visited if self.times_visited != 0 else 0\n",
    "\n",
    "    def ucb_score(self, scale=10, max_value=1e100):\n",
    "        \"\"\"\n",
    "        Computes ucb1 upper bound using current value and visit counts for node and it's parent.\n",
    "\n",
    "        :param scale: Multiplies upper bound by that. From hoeffding inequality, assumes reward range to be [0,scale].\n",
    "        :param max_value: a value that represents infinity (for unvisited nodes)\n",
    "\n",
    "        \"\"\"\n",
    "\n",
    "        if self.times_visited == 0:\n",
    "            return max_value\n",
    "\n",
    "        # compute ucb-1 additive component (to be added to mean value)\n",
    "        # hint: you can use self.parent.times_visited for N times node was considered,\n",
    "        # and self.times_visited for n times it was visited\n",
    "\n",
    "        U = <your code here >\n",
    "\n",
    "        return self.get_mean_value() + scale*U\n",
    "\n",
    "    # MCTS steps\n",
    "\n",
    "    def select_best_leaf(self):\n",
    "        \"\"\"\n",
    "        Picks the leaf with highest priority to expand\n",
    "        Does so by recursively picking nodes with best UCB-1 score until it reaches the leaf.\n",
    "\n",
    "        \"\"\"\n",
    "        if self.is_leaf():\n",
    "            return self\n",
    "\n",
    "        children = self.children\n",
    "\n",
    "        best_child = <select best child node in terms of node.ucb_score() >\n",
    "\n",
    "        return best_child.select_best_leaf()\n",
    "\n",
    "    def expand(self):\n",
    "        \"\"\"\n",
    "        Expands the current node by creating all possible child nodes.\n",
    "        Then returns one of those children.\n",
    "        \"\"\"\n",
    "\n",
    "        assert not self.is_done, \"can't expand from terminal state\"\n",
    "\n",
    "        for action in range(n_actions):\n",
    "            self.children.add(Node(self, action))\n",
    "\n",
    "        return self.select_best_leaf()\n",
    "\n",
    "    def rollout(self, t_max=10**4):\n",
    "        \"\"\"\n",
    "        Play the game from this state to the end (done) or for t_max steps.\n",
    "\n",
    "        On each step, pick action at random (hint: env.action_space.sample()).\n",
    "\n",
    "        Compute sum of rewards from current state till \n",
    "        Note 1: use env.action_space.sample() for random action\n",
    "        Note 2: if node is terminal (self.is_done is True), just return 0\n",
    "\n",
    "        \"\"\"\n",
    "\n",
    "        # set env into the appropriate state\n",
    "        env.load_snapshot(self.snapshot)\n",
    "        obs = self.observation\n",
    "        is_done = self.is_done\n",
    "\n",
    "        <your code here - rollout and compute reward >\n",
    "\n",
    "        return rollout_reward\n",
    "\n",
    "    def propagate(self, child_value):\n",
    "        \"\"\"\n",
    "        Uses child value (sum of rewards) to update parents recursively.\n",
    "        \"\"\"\n",
    "        # compute node value\n",
    "        my_value = self.immediate_reward + child_value\n",
    "\n",
    "        # update value_sum and times_visited\n",
    "        self.value_sum += my_value\n",
    "        self.times_visited += 1\n",
    "\n",
    "        # propagate upwards\n",
    "        if not self.is_root():\n",
    "            self.parent.propagate(my_value)\n",
    "\n",
    "    def safe_delete(self):\n",
    "        \"\"\"safe delete to prevent memory leak in some python versions\"\"\"\n",
    "        del self.parent\n",
    "        for child in self.children:\n",
    "            child.safe_delete()\n",
    "            del child"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Root(Node):\n",
    "    def __init__(self, snapshot, observation):\n",
    "        \"\"\"\n",
    "        creates special node that acts like tree root\n",
    "        :snapshot: snapshot (from env.get_snapshot) to start planning from\n",
    "        :observation: last environment observation\n",
    "        \"\"\"\n",
    "\n",
    "        self.parent = self.action = None\n",
    "        self.children = set()  # set of child nodes\n",
    "\n",
    "        # root: load snapshot and observation\n",
    "        self.snapshot = snapshot\n",
    "        self.observation = observation\n",
    "        self.immediate_reward = 0\n",
    "        self.is_done = False\n",
    "\n",
    "    @staticmethod\n",
    "    def from_node(node):\n",
    "        \"\"\"initializes node as root\"\"\"\n",
    "        root = Root(node.snapshot, node.observation)\n",
    "        # copy data\n",
    "        copied_fields = [\"value_sum\", \"times_visited\", \"children\", \"is_done\"]\n",
    "        for field in copied_fields:\n",
    "            setattr(root, field, getattr(node, field))\n",
    "        return root"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Main MCTS loop\n",
    "\n",
    "With all we implemented, MCTS boils down to a trivial piece of code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plan_mcts(root, n_iters=10):\n",
    "    \"\"\"\n",
    "    builds tree with monte-carlo tree search for n_iters iterations\n",
    "    :param root: tree node to plan from\n",
    "    :param n_iters: how many select-expand-simulate-propagete loops to make\n",
    "    \"\"\"\n",
    "    for _ in range(n_iters):\n",
    "\n",
    "        node = <select best leaf >\n",
    "\n",
    "        if node.is_done:\n",
    "            node.propagate(0)\n",
    "\n",
    "        else:  # node is not terminal\n",
    "            <expand-simulate-propagate loop >"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plan and execute\n",
    "In this section, we use the MCTS implementation to find optimal policy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "env = WithSnapshots(gym.make(\"CartPole-v0\"))\n",
    "root_observation = env.reset()\n",
    "root_snapshot = env.get_snapshot()\n",
    "root = Root(root_snapshot, root_observation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# plan from root:\n",
    "plan_mcts(root, n_iters=1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import copy\n",
    "# saved_root = copy.deepcopy(root)\n",
    "# root = saved_root"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import clear_output\n",
    "from itertools import count\n",
    "from gym.wrappers import Monitor\n",
    "\n",
    "total_reward = 0  # sum of rewards\n",
    "test_env = loads(root_snapshot)  # env used to show progress\n",
    "\n",
    "for i in count():\n",
    "\n",
    "    # get best child\n",
    "    best_child = <select child with highest mean reward >\n",
    "\n",
    "    # take action\n",
    "    s, r, done, _ = test_env.step(best_child.action)\n",
    "\n",
    "    # show image\n",
    "    clear_output(True)\n",
    "    plt.title(\"step %i\" % i)\n",
    "    plt.imshow(test_env.render('rgb_array'))\n",
    "    plt.show()\n",
    "\n",
    "    total_reward += r\n",
    "    if done:\n",
    "        print(\"Finished with reward = \", total_reward)\n",
    "        break\n",
    "\n",
    "    # discard unrealized part of the tree [because not every child matters :(]\n",
    "    for child in root.children:\n",
    "        if child != best_child:\n",
    "            child.safe_delete()\n",
    "\n",
    "    # declare best child a new root\n",
    "    root = Root.from_node(best_child)\n",
    "\n",
    "    assert not root.is_leaf(\n",
    "    ), \"We ran out of tree! Need more planning! Try growing tree right inside the loop.\"\n",
    "\n",
    "    # you may want to expand tree here\n",
    "    # <your code here>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Bonus assignments (10+pts each)\n",
    "\n",
    "There's a few things you might want to try if you want to dig deeper:\n",
    "\n",
    "### Node selection and expansion\n",
    "\n",
    "\"Analyze this\" assignment\n",
    "\n",
    "UCB-1 is a weak bound as it relies on a very general bounds (Hoeffding Inequality, to be exact). \n",
    "* Try playing with alpha. The theoretically optimal alpha for CartPole is 200 (max reward). \n",
    "* Use using a different exploration strategy (bayesian UCB, for example)\n",
    "* Expand not all but several random actions per `expand` call. See __the notes below__ for details.\n",
    "\n",
    "The goal is to find out what gives the optimal performance for `CartPole-v0` for different time budgets (i.e. different n_iter in plan_mcts.\n",
    "\n",
    "Evaluate your results on `Acrobot-v1` - do the results change and if so, how can you explain it?\n",
    "\n",
    "\n",
    "### Atari-RAM\n",
    "\n",
    "\"Build this\" assignment\n",
    "\n",
    "Apply MCTS to play atari games. In particular, let's start with ```gym.make(\"MsPacman-ramDeterministic-v0\")```.\n",
    "\n",
    "This requires two things:\n",
    "* Slightly modify WithSnapshots wrapper to work with atari.\n",
    "\n",
    " * Atari has a special interface for snapshots:\n",
    "   ```   \n",
    "   snapshot = self.env.ale.cloneState()\n",
    "   ...\n",
    "   self.env.ale.restoreState(snapshot)\n",
    "   ```\n",
    " * Try it on the env above to make sure it does what you told it to.\n",
    " \n",
    "* Run MCTS on the game above. \n",
    " * Start with small tree size to speed-up computations\n",
    " * You will probably want to rollout for 10-100 steps (t_max) for starters\n",
    " * Consider using discounted rewards (see __notes at the end__)\n",
    " * Try a better rollout policy\n",
    " \n",
    " \n",
    "### Integrate learning into planning\n",
    "\n",
    "Planning on each iteration is a costly thing to do. You can speed things up drastically if you train a classifier to predict which action will turn out to be best according to MCTS.\n",
    "\n",
    "To do so, just record which action did the MCTS agent take on each step and fit something to [state, mcts_optimal_action]\n",
    "* You can also use optimal actions from discarded states to get more (dirty) samples. Just don't forget to fine-tune without them.\n",
    "* It's also worth a try to use P(best_action|state) from your model to select best nodes in addition to UCB\n",
    "* If your model is lightweight enough, try using it as a rollout policy.\n",
    "\n",
    "__(bonus points)__ While CartPole is glorious enough, try expanding this to ```gym.make(\"MsPacmanDeterministic-v0\")```\n",
    "* See previous section on how to wrap atari (you'll get points for both if you run this on atari)\n",
    "\n",
    "\n",
    "### Integrate planning into learning (project, a LOT of points)\n",
    "\n",
    "Incorporate planning into the agent architecture. \n",
    "\n",
    "The goal is to implement [Value Iteration Networks](https://arxiv.org/abs/1602.02867)\n",
    "\n",
    "You will need to implement a special layer that performs value iteration-like update to a recurrent memory. This can be implemented the same way you did in the POMDP assignment."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Notes\n",
    "\n",
    "\n",
    "#### Assumptions\n",
    "\n",
    "The full list of assumptions is\n",
    "* __Finite actions__ - we enumerate all actions in `expand`\n",
    "* __Episodic (finite) MDP__ - while technically it works for infinite mdp, we rollout for $ 10^4$ steps. If you are knowingly infinite, please adjust `t_max` to something more reasonable.\n",
    "* __No discounted rewards__ - we assume $\\gamma=1$. If that isn't the case, you only need to change two lines in `rollout` and use `my_R = r + gamma*child_R` for `propagate`\n",
    "* __pickleable env__ - won't work if e.g. your env is connected to a web-browser surfing the internet. For custom envs, you may need to modify get_snapshot/load_snapshot from `WithSnapshots`.\n",
    "\n",
    "#### On `get_best_leaf` and `expand` functions\n",
    "\n",
    "This MCTS implementation only selects leaf nodes for expansion.\n",
    "This doesn't break things down because `expand` adds all possible actions. Hence, all non-leaf nodes are by design fully expanded and shouldn't be selected.\n",
    "\n",
    "If you want to only add a few random action on each expand, you will also have to modify `get_best_leaf` to consider returning non-leafs.\n",
    "\n",
    "#### Rollout policy\n",
    "\n",
    "We use a simple uniform policy for rollouts. This introduces a negative bias to good situations that can be messed up completely with random bad action. As a simple example, if you tend to rollout with uniform policy, you better don't use sharp knives and walk near cliffs.\n",
    "\n",
    "You can improve that by integrating a reinforcement _learning_ algorithm with a computationally light agent. You can even train this agent on optimal policy found by the tree search.\n",
    "\n",
    "#### Contributions\n",
    "* Reusing some code from 5vision [solution for deephack.RL](https://github.com/5vision/uct_atari), code by Mikhail Pavlov\n",
    "* Using some code from [this gist](https://gist.github.com/blole/dfebbec182e6b72ec16b66cc7e331110)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
